package ssm.ann.controller;

import lombok.extern.java.Log;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.async.DeferredResult;
import org.springframework.web.context.request.async.WebAsyncTask;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * https://blog.csdn.net/f641385712/article/details/88692534
 * https://blog.csdn.net/f641385712/article/details/88710676
 */
@Log
@Controller
@RequestMapping("/async/controller")
public class My_07_AsyncController {
    @ResponseBody
    @GetMapping("/hello1")
    public Callable<String> helloGet1() throws Exception {
        System.out.println(Thread.currentThread().getName() + " 主线程start");

        Callable<String> callable = () -> {
            System.out.println(Thread.currentThread().getName() + " 子子子线程start");
            TimeUnit.SECONDS.sleep(5); //模拟处理业务逻辑，话费了5秒钟
            System.out.println(Thread.currentThread().getName() + " 子子子线程end");

            // 这里稍微小细节一下：最终返回的不是 Callable 对象，而是它里面的内容
            return "hello world";
        };

        System.out.println(Thread.currentThread().getName() + " 主线程end");
        return callable;
    }

    /**
     * 实际使用中，不建议直接使用 Callable，而是使用 Spring 提供的 WebAsyncTask 代替，它包装了 Callable，功能更强大些
     */
    @ResponseBody
    @GetMapping("/hello2")
    public WebAsyncTask<String> helloGet2() throws Exception {
        System.out.println(Thread.currentThread().getName() + " 主线程start");

        Callable<String> callable = () -> {
            System.out.println(Thread.currentThread().getName() + " 子子子线程start");
            TimeUnit.SECONDS.sleep(5); //模拟处理业务逻辑，话费了5秒钟
            System.out.println(Thread.currentThread().getName() + " 子子子线程end");

            return "hello world";
        };

        // 采用 WebAsyncTask 返回，这样可以处理超时和错误，同时也可以指定使用的 Excutor 名称
        WebAsyncTask<String> webAsyncTask = new WebAsyncTask<>(3000, callable);
        // 注意：onCompletion 表示完成，不管你是否超时、是否抛出异常，这个函数都会执行的
        webAsyncTask.onCompletion(() -> System.out.println("程序[正常执行]完成的回调"));

        // 这两个返回的内容，最终都会放进 response 里面去
        webAsyncTask.onTimeout(() -> "程序[超时]的回调");
        // 备注：这个是 Spring5 新增的
        webAsyncTask.onError(() -> "程序[出现异常]的回调");

        System.out.println(Thread.currentThread().getName() + " 主线程end");
        return webAsyncTask;
    }

    private List<DeferredResult<String>> deferredResultList = new ArrayList<>();

    @ResponseBody
    @GetMapping("/hello3")
    public DeferredResult<String> helloGet3() throws Exception {
        DeferredResult<String> deferredResult = new DeferredResult<>();
        // 先存起来，等待触发
        deferredResultList.add(deferredResult);
        return deferredResult;
    }

    @ResponseBody
    @GetMapping("/setHello3ToAll")
    public void helloSet() throws Exception {
        // 让所有 hold 住的请求给与响应
        deferredResultList.forEach(d -> d.setResult("say hello to all"));
    }


    // 值为 List，因为监视同一个名称空间的长轮询可能有 N 个（毕竟可能有多个客户端用同一份配置）
    private Map<String, List<DeferredResult<String>>> watchRequests = new ConcurrentHashMap<>();

    @GetMapping(value = "/all/watchrequests")
    public Object getWatchRequests() {
        return watchRequests;
    }

    // 模拟长轮询：apollo 客户端来监听配置文件的变更，可以指定 namespace 监视指定的 NameSpace
    @GetMapping(value = "/watch/{namespace}")
    public DeferredResult<String> watch(@PathVariable("namespace") String namespace) {
        log.info("Request received,namespace is" + namespace + ",当前时间：" + System.currentTimeMillis());
        DeferredResult<String> deferredResult = new DeferredResult<>();

        // 当 deferredResult 完成时（不论是超时还是异常还是正常完成），都应该移除 watchRequests 中相应的 watch key
        deferredResult.onCompletion(() -> {
            log.info("onCompletion，移除对namespace：" + namespace + "的监视~");
            List<DeferredResult<String>> list = watchRequests.get(namespace);
            list.remove(deferredResult);
            if (list.isEmpty()) {
                watchRequests.remove(namespace);
            }
        });

        List<DeferredResult<String>> list = watchRequests.computeIfAbsent(namespace, (k) -> new ArrayList<>());
        list.add(deferredResult);
        return deferredResult;
    }

    // 模拟发布 namespace 配置：修改配置
    @GetMapping(value = "/publish/{namespace}")
    public void publishConfig(@PathVariable("namespace") String namespace) {
        //do Something for update config
        if (watchRequests.containsKey(namespace)) {
            List<DeferredResult<String>> deferredResults = watchRequests.get(namespace);
            // 通知所有 watch 这个 namespace 变更的长轮训配置变更结果
            for (DeferredResult<String> deferredResult : deferredResults) {
                deferredResult.setResult(namespace + " changed，时间为" + System.currentTimeMillis());
            }
        }
    }
}
